package SQLite3;

import java.io.*;

/**
 * Internal class implementing java.io.InputStream on SQLite 3.4.0 incremental
 * blob I/O interface.
 */

class BlobR extends InputStream {

	/**
	 * Blob instance
	 */

	private Blob blob;

	/**
	 * Read position, file pointer.
	 */

	private int pos;

	/**
	 * Contruct InputStream from blob instance.
	 */

	BlobR(Blob blob) {
		this.blob = blob;
		this.pos = 0;
	}

	/**
	 * Return number of available bytes for reading.
	 * 
	 * @return available input bytes
	 */

	public int available() throws IOException {
		int ret = blob.size - pos;
		return (ret < 0) ? 0 : ret;
	}

	/**
	 * Mark method; dummy to satisfy InputStream class.
	 */

	public void mark(int limit) {
	}

	/**
	 * Reset method; dummy to satisfy InputStream class.
	 */

	public void reset() throws IOException {
	}

	/**
	 * Mark support; not for this class.
	 * 
	 * @return always false
	 */

	public boolean markSupported() {
		return false;
	}

	/**
	 * Close this blob InputStream.
	 */

	public void close() throws IOException {
		blob.close();
		blob = null;
		pos = 0;
	}

	/**
	 * Skip over blob data.
	 */

	public long skip(long n) throws IOException {
		long ret = pos + n;
		if (ret < 0) {
			ret = 0;
			pos = 0;
		} else if (ret > blob.size) {
			ret = blob.size;
			pos = blob.size;
		} else {
			pos = (int) ret;
		}
		return ret;
	}

	/**
	 * Read single byte from blob.
	 * 
	 * @return byte read
	 */

	public int read() throws IOException {
		byte b[] = new byte[1];
		int n = blob.read(b, 0, pos, b.length);
		if (n > 0) {
			pos += n;
			return b[0];
		}
		return -1;
	}

	/**
	 * Read byte array from blob.
	 * 
	 * @param b
	 *            byte array to be filled
	 * @return number of bytes read
	 */

	public int read(byte b[]) throws IOException {
		int n = blob.read(b, 0, pos, b.length);
		if (n > 0) {
			pos += n;
			return n;
		}
		return -1;
	}

	/**
	 * Read slice of byte array from blob.
	 * 
	 * @param b
	 *            byte array to be filled
	 * @param off
	 *            offset into byte array
	 * @param len
	 *            length to be read
	 * @return number of bytes read
	 */

	public int read(byte b[], int off, int len) throws IOException {
		if (off + len > b.length) {
			len = b.length - off;
		}
		if (len < 0) {
			return -1;
		}
		if (len == 0) {
			return 0;
		}
		int n = blob.read(b, off, pos, len);
		if (n > 0) {
			pos += n;
			return n;
		}
		return -1;
	}
}

/**
 * Internal class implementing java.io.OutputStream on SQLite 3.4.0 incremental
 * blob I/O interface.
 */

class BlobW extends OutputStream {

	/**
	 * Blob instance
	 */

	private Blob blob;

	/**
	 * Read position, file pointer.
	 */

	private int pos;

	/**
	 * Contruct OutputStream from blob instance.
	 */

	BlobW(Blob blob) {
		this.blob = blob;
		this.pos = 0;
	}

	/**
	 * Flush blob; dummy to satisfy OutputStream class.
	 */

	public void flush() throws IOException {
	}

	/**
	 * Close this blob OutputStream.
	 */

	public void close() throws IOException {
		blob.close();
		blob = null;
		pos = 0;
	}

	/**
	 * Write blob data.
	 * 
	 * @param v
	 *            byte to be written at current position.
	 */

	public void write(int v) throws IOException {
		byte b[] = new byte[1];
		b[0] = (byte) v;
		pos += blob.write(b, 0, pos, 1);
	}

	/**
	 * Write blob data.
	 * 
	 * @param b
	 *            byte array to be written at current position.
	 */

	public void write(byte[] b) throws IOException {
		if (b != null && b.length > 0) {
			pos += blob.write(b, 0, pos, b.length);
		}
	}

	/**
	 * Write blob data.
	 * 
	 * @param b
	 *            byte array to be written.
	 * @param off
	 *            offset within byte array
	 * @param len
	 *            length of data to be written
	 */

	public void write(byte[] b, int off, int len) throws IOException {
		if (b != null) {
			if (off + len > b.length) {
				len = b.length - off;
			}
			if (len <= 0) {
				return;
			}
			pos += blob.write(b, off, pos, len);
		}
	}
}

/**
 * Class to represent SQLite3 3.4.0 incremental blob I/O interface.
 * 
 * Note, that all native methods of this class are not synchronized, i.e. it is
 * up to the caller to ensure that only one thread is in these methods at any
 * one time.
 */

public class Blob {

	/**
	 * Internal handle for the SQLite3 blob.
	 */

	private long handle = 0;

	/**
	 * Cached size of blob, setup right after blob has been opened.
	 */

	protected int size = 0;

	/**
	 * Return InputStream for this blob
	 * 
	 * @return InputStream
	 */

	public InputStream getInputStream() {
		return (InputStream) new BlobR(this);
	}

	/**
	 * Return OutputStream for this blob
	 * 
	 * @return OutputStream
	 */

	public OutputStream getOutputStream() {
		return (OutputStream) new BlobW(this);
	}

	/**
	 * Close blob.
	 */

	public native void close();

	/**
	 * Internal blob write method.
	 * 
	 * @param b
	 *            byte array to be written
	 * @param off
	 *            offset into byte array
	 * @param pos
	 *            offset into blob
	 * @param len
	 *            length to be written
	 * @return number of bytes written to blob
	 */

	native int write(byte[] b, int off, int pos, int len) throws IOException;

	/**
	 * Internal blob read method.
	 * 
	 * @param b
	 *            byte array to be written
	 * @param off
	 *            offset into byte array
	 * @param pos
	 *            offset into blob
	 * @param len
	 *            length to be written
	 * @return number of bytes written to blob
	 */

	native int read(byte[] b, int off, int pos, int len) throws IOException;

	/**
	 * Destructor for object.
	 */

	protected native void finalize();

	/**
	 * Internal native initializer.
	 */

	private static native void internal_init();

	static {
		internal_init();
	}
}
